//
//  GLAnimationSet.m
//  LMPhotoVideoSDK
//
//  Created by Aimar on 16/7/20.
//  Copyright © 2016年 魁钺会. All rights reserved.
//

#import "GLAnimationSet.h"

#define PROPERTY_FILL_AFTER_MASK         0x1
#define PROPERTY_FILL_BEFORE_MASK        0x2
#define PROPERTY_REPEAT_MODE_MASK        0x4
#define PROPERTY_START_OFFSET_MASK       0x8
#define PROPERTY_SHARE_INTERPOLATOR_MASK 0x10
#define PROPERTY_DURATION_MASK           0x20
#define PROPERTY_MORPH_MATRIX_MASK       0x40
#define PROPERTY_CHANGE_BOUNDS_MASK      0x80

@interface GLAnimationSet ()

@property (nonatomic, assign)int                            flags;
@property (nonatomic, strong)NSMutableArray<GLAnimation*>*  animations;
@property (nonatomic, assign)long                       lastEnd;
@property (nonatomic, assign)long                       storedOffsets[];
@end

@implementation GLAnimationSet

- (id) initWithShareInterpolator:(BOOL)share {
    self = [super init];
    if (self) {
        self.animations = (NSMutableArray<GLAnimation*>*)[[NSMutableArray alloc] initWithCapacity:5];
        self.startTime = 0;
        self.duration = 0;
        
        [self setFlag:PROPERTY_SHARE_INTERPOLATOR_MASK value:share];
    }
    
    return self;
}

- (void)setFlag:(int)mask value:(BOOL)v {
    if (v) {
        _flags |= mask;
    } else {
        _flags &= ~mask;
    }
}

- (void) setFillAfter:(BOOL)fillAfter {
    _flags |= PROPERTY_FILL_AFTER_MASK;
    [super setFillAfter:fillAfter];
}

- (void) setFillBefore:(BOOL)fillBefore {
    _flags |= PROPERTY_FILL_BEFORE_MASK;
    [super setFillBefore:fillBefore];
}

- (void) setRepeatMode:(GLAnimationRepeatMode)repeatMode {
    _flags |= PROPERTY_REPEAT_MODE_MASK;
    [super setRepeatMode:repeatMode];
}

- (void) setStartOffset:(long)startOffset {
    _flags |= PROPERTY_START_OFFSET_MASK;
    [super setStartOffset:startOffset];
}

- (void) setDuration:(long)durationMillis {
    _flags |= PROPERTY_DURATION_MASK;
    [super setDuration:durationMillis];
}

- (void)addAnimation:(GLAnimation *)am {
    [self.animations addObject:am];
    
    BOOL noMatrix = (_flags & PROPERTY_MORPH_MATRIX_MASK) == 0;
    if (noMatrix && [am willChangeTransformationMatrix]) {
        _flags |= PROPERTY_MORPH_MATRIX_MASK;
    }
    
    BOOL changeBounds = (_flags & PROPERTY_CHANGE_BOUNDS_MASK) == 0;
    if (changeBounds && [am willChangeTransformationMatrix]) {
        _flags |= PROPERTY_CHANGE_BOUNDS_MASK;
    }
    
    if (self.animations.count == 1) {
        self.duration = am.startOffset + am.duration;
        self.lastEnd = self.startOffset + self.duration;
    } else {
        self.lastEnd = MAX(self.lastEnd, am.startOffset + am.duration);
        self.duration = self.lastEnd - self.startOffset;
    }
}

- (void)setStartTime:(long)startTime {
    [super setStartTime:startTime];
    
    const int count = self.animations.count;
    for (int i = 0; i < count; i++) {
        GLAnimation* am = [self.animations objectAtIndex:(i)];
        [am setStartTime:startTime];
    }
}

- (long)getStartTime {
    long startTime = 0x8000000000000000L;
    
    const int count = self.animations.count;
    for (int i = 0; i < count; i++) {
        GLAnimation* am = [self.animations objectAtIndex:(i)];
        startTime = MIN(startTime, am.startTime);
    }
    
    return startTime;
}

- (long)getDuration {
    final ArrayList<Animation> animations = mAnimations;
    const int count = self.animations.count;
    long duration = 0;
    
    BOOL durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
    if (durationSet) {
        duration = mDuration;
    } else {
        for (int i = 0; i < count; i++) {
            duration = MAX(duration, [self.animations objectAtIndex:i].duration);
        }
    }
    
    return duration;
}

/**
 * The duration hint of an animation set is the maximum of the duration
 * hints of all of its component animations.
 *
 * @see android.view.animation.Animation#computeDurationHint
 */
public long computeDurationHint() {
    long duration = 0;
    final int count = mAnimations.size();
    final ArrayList<Animation> animations = mAnimations;
    for (int i = count - 1; i >= 0; --i) {
        final long d = animations.get(i).computeDurationHint();
        if (d > duration) {
            duration = d;
        }
    }
    return duration;
}

/**
 * @hide
 */
public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
    final RectF region = mPreviousRegion;
    region.set(left, top, right, bottom);
    region.inset(-1.0f, -1.0f);
    
    if (mFillBefore) {
        final int count = mAnimations.size();
        final ArrayList<Animation> animations = mAnimations;
        final Transformation3D temp = mTempTransformation;
        
        final Transformation3D previousTransformation = mPreviousTransformation;
        
        for (int i = count - 1; i >= 0; --i) {
            final Animation a = animations.get(i);
            
            temp.clear();
            final Interpolator interpolator = a.mInterpolator;
            a.applyTransformation(interpolator != null ? interpolator.getInterpolation(0)
                                  : 0, temp);
            previousTransformation.compose(temp);
        }
    }
}

- (BOOL)getTransformation:(long)currentTime outTransform:(mat4*)transform {
    _currentTime = currentTime;
    if (self.duration != 0) {
        _normalizeTime = (currentTime - (self.startTime + self.startOffset)) / (float) self.duration;
    } else {
        _normalizeTime = currentTime < self.startTime ? 0.0f : 1.0f;
    }
    _normalizeTime = MAX(MIN(_normalizeTime, 1.0f), 0.0f);
    if (_cycleFlip) {
        _normalizeTime = 1.0f - _normalizeTime;
    }
    
    
    const int count = mAnimations.size();
    const NSArray<GLAnimation*>* animations = self.animations;
    final Transformation3D temp = mTempTransformation;
    
    boolean more = false;
    boolean started = false;
    boolean ended = true;
    
    t.clear();
    
    for (int i = count - 1; i >= 0; --i) {
        final Animation a = animations.get(i);
        
        temp.clear();
        more = a.getTransformation(currentTime, temp) || more;
        t.compose(temp);
        
        started = started || a.hasStarted();
        ended = a.hasEnded() && ended;
    }
    
    if (started && !mStarted) {
        if (mListener != null) {
            mListener.onAnimationStart(this);
        }
        mStarted = true;
    }
    
    if (ended != mEnded) {
        if (mListener != null) {
            mListener.onAnimationEnd(this);
        }
        mEnded = ended;
    }
    
    return more;
}

/**
 * @see android.view.animation.Animation#scaleCurrentDuration(float)
 */
@Override
public void scaleCurrentDuration(float scale) {
    final ArrayList<Animation> animations = mAnimations;
    int count = animations.size();
    for (int i = 0; i < count; i++) {
        animations.get(i).scaleCurrentDuration(scale);
    }
}

/**
 * @see android.view.animation.Animation#initialize(int, int, int, int)
 */
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
    super.initialize(width, height, parentWidth, parentHeight);
    
    boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
    boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK;
    boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK;
    boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK;
    boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK)
    == PROPERTY_SHARE_INTERPOLATOR_MASK;
    boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK)
    == PROPERTY_START_OFFSET_MASK;
    
    if (shareInterpolator) {
        ensureInterpolator();
    }
    
    final ArrayList<Animation> children = mAnimations;
    final int count = children.size();
    
    final long duration = mDuration;
    final boolean fillAfter = mFillAfter;
    final boolean fillBefore = mFillBefore;
    final int repeatMode = mRepeatMode;
    final Interpolator interpolator = mInterpolator;
    final long startOffset = mStartOffset;
    
    
    long[] storedOffsets = mStoredOffsets;
    if (startOffsetSet) {
        if (storedOffsets == null || storedOffsets.length != count) {
            storedOffsets = mStoredOffsets = new long[count];
        }
    } else if (storedOffsets != null) {
        storedOffsets = mStoredOffsets = null;
    }
    
    for (int i = 0; i < count; i++) {
        Animation a = children.get(i);
        if (durationSet) {
            a.setDuration(duration);
        }
        if (fillAfterSet) {
            a.setFillAfter(fillAfter);
        }
        if (fillBeforeSet) {
            a.setFillBefore(fillBefore);
        }
        if (repeatModeSet) {
            a.setRepeatMode(repeatMode);
        }
        if (shareInterpolator) {
            a.setInterpolator(interpolator);
        }
        if (startOffsetSet) {
            long offset = a.getStartOffset();
            a.setStartOffset(offset + startOffset);
            storedOffsets[i] = offset;
        }
        a.initialize(width, height, parentWidth, parentHeight);
    }
}

@Override
public void reset() {
    super.reset();
    restoreChildrenStartOffset();
}

/**
 * @hide
 */
void restoreChildrenStartOffset() {
    final long[] offsets = mStoredOffsets;
    if (offsets == null) {
        return;
    }
    
    final ArrayList<Animation> children = mAnimations;
    final int count = children.size();
    
    for (int i = 0; i < count; i++) {
        children.get(i).setStartOffset(offsets[i]);
    }
}

- (NSArray*) getAnimations {
    return self.animations;
}

- (BOOL)willChangeTransformationMatrix {
    return (_flags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK;
}

- (BOOL)willChangeBounds {
    return (_flags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
}

@end
